/**
 * @copyright Copyright (c) 2025 CSG Digital Grid Technology (Guangdong) Co., Ltd.
 * @author Chen Yongjie
 */
#include "cJSON.h"
#include "cJSON_Utils.h"
#include <assert.h>
#include <string.h>

/* Helper to compare two JSONs */
static void assert_json_equal(cJSON *a, cJSON *b) {
    assert(cJSON_Compare(a, b, true) == 1);
}

/* Test cJSONUtils_FindPointerFromObjectTo */
void test_cJSONUtils_FindPointerFromObjectTo(void) {
    cJSON *root = cJSON_Parse("{\"a\": {\"b\": [1, 2, {\"c\": \"target\"}]}}");
    cJSON *target = cJSON_GetObjectItemCaseSensitive(
        cJSON_GetArrayItem(
            cJSON_GetObjectItemCaseSensitive(
                cJSON_GetObjectItemCaseSensitive(root, "a"), "b"), 2),
        "c"
    );
    char *ptr = cJSONUtils_FindPointerFromObjectTo(root, target);
    assert(ptr != NULL);
    assert(strcmp(ptr, "/a/b/2/c") == 0);
    cJSON_free(ptr);
    cJSON_Delete(root);
}

/* Test cJSONUtils_GetPointer */
void test_cJSONUtils_GetPointer(void) {
    cJSON *root = cJSON_Parse("{\"foo\": [\"bar\", {\"baz\": 42}]}");
    cJSON *item = cJSONUtils_GetPointer(root, "/foo/1/baz");
    assert(item != NULL);
    assert(cJSON_IsNumber(item));
    assert(item->valueint == 42);
    cJSON_Delete(root);
}

/* Test cJSONUtils_GetPointerCaseSensitive */
void test_cJSONUtils_GetPointerCaseSensitive(void) {
    cJSON *root = cJSON_Parse("{\"Foo\": 1}");
    cJSON *item1 = cJSONUtils_GetPointer(root, "/foo");         // case-insensitive match
    cJSON *item2 = cJSONUtils_GetPointerCaseSensitive(root, "/foo"); // no match
    assert(item1 != NULL);
    assert(item2 == NULL);
    cJSON_Delete(root);
}

/* Test cJSONUtils_GeneratePatches */
void test_cJSONUtils_GeneratePatches(void) {
    cJSON *from = cJSON_Parse("{\"a\": 1, \"b\": 2}");
    cJSON *to   = cJSON_Parse("{\"a\": 1, \"b\": 3, \"c\": 4}");
    cJSON *patches = cJSONUtils_GeneratePatches(from, to);
    assert(cJSON_GetArraySize(patches) == 2);

    cJSON *p1 = cJSON_GetArrayItem(patches, 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p1, "op")->valuestring, "replace") == 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p1, "path")->valuestring, "/b") == 0);
    assert(cJSON_GetObjectItemCaseSensitive(p1, "value")->valueint == 3);

    cJSON *p2 = cJSON_GetArrayItem(patches, 1);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p2, "op")->valuestring, "add") == 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p2, "path")->valuestring, "/c") == 0);
    assert(cJSON_GetObjectItemCaseSensitive(p2, "value")->valueint == 4);

    cJSON_Delete(from);
    cJSON_Delete(to);
    cJSON_Delete(patches);
}

/* Test cJSONUtils_ApplyPatches */
void test_cJSONUtils_ApplyPatches(void) {
    cJSON *obj = cJSON_Parse("{\"a\": 1}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"add\", \"path\": \"/b\", \"value\": 2}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result == 0);
    assert(cJSON_GetObjectItemCaseSensitive(obj, "b")->valueint == 2);
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test cJSONUtils_MergePatch */
void test_cJSONUtils_MergePatch(void) {
    cJSON *target = cJSON_Parse("{\"a\": 1, \"b\": 2}");
    cJSON *patch  = cJSON_Parse("{\"b\": null, \"c\": 3}");
    cJSON *result = cJSONUtils_MergePatch(target, patch);
    assert(result != NULL);
    assert(cJSON_GetObjectItemCaseSensitive(result, "a") != NULL);
    assert(cJSON_GetObjectItemCaseSensitive(result, "b") == NULL);
    assert(cJSON_GetObjectItemCaseSensitive(result, "c")->valueint == 3);
    cJSON_Delete(patch);
    // target is consumed by MergePatch, so only delete result
    cJSON_Delete(result);
}

/* Test cJSONUtils_GenerateMergePatch */
void test_cJSONUtils_GenerateMergePatch(void) {
    cJSON *from = cJSON_Parse("{\"a\": 1, \"b\": 2}");
    cJSON *to   = cJSON_Parse("{\"a\": 1, \"c\": 3}");
    cJSON *merge_patch = cJSONUtils_GenerateMergePatch(from, to);
    assert(merge_patch != NULL);
    assert(cJSON_IsObject(merge_patch));
    assert(cJSON_GetObjectItemCaseSensitive(merge_patch, "b") != NULL);
    assert(cJSON_IsNull(cJSON_GetObjectItemCaseSensitive(merge_patch, "b")));
    assert(cJSON_GetObjectItemCaseSensitive(merge_patch, "c")->valueint == 3);
    cJSON_Delete(from);
    cJSON_Delete(to);
    cJSON_Delete(merge_patch);
}

/* Test cJSONUtils_SortObject */
void test_cJSONUtils_SortObject(void) {
    cJSON *obj = cJSON_Parse("{\"z\":1,\"a\":2,\"m\":3}");
    cJSONUtils_SortObject(obj);
    cJSON *child = obj->child;
    assert(strcmp(child->string, "a") == 0);
    child = child->next;
    assert(strcmp(child->string, "m") == 0);
    child = child->next;
    assert(strcmp(child->string, "z") == 0);
    cJSON_Delete(obj);
}


/* Test ApplyPatches: remove root */
void test_cJSONUtils_ApplyPatches_remove_root(void) {
    cJSON *obj = cJSON_Parse("{\"x\": 1}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"remove\", \"path\": \"\"}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result == 0);
    assert(obj->type == cJSON_Invalid); // root removed → invalid
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test ApplyPatches: "test" operation */
void test_cJSONUtils_ApplyPatches_test_op(void) {
    cJSON *obj = cJSON_Parse("{\"a\": 42}");
    cJSON *patch_pass = cJSON_Parse("[{\"op\": \"test\", \"path\": \"/a\", \"value\": 42}]");
    cJSON *patch_fail = cJSON_Parse("[{\"op\": \"test\", \"path\": \"/a\", \"value\": 43}]");

    assert(cJSONUtils_ApplyPatches(obj, patch_pass) == 0);   // success → returns 0
    assert(cJSONUtils_ApplyPatches(obj, patch_fail) != 0);   // fail → non-zero

    cJSON_Delete(obj);
    cJSON_Delete(patch_pass);
    cJSON_Delete(patch_fail);
}

/* Test ApplyPatches: "move" operation */
void test_cJSONUtils_ApplyPatches_move_op(void) {
    cJSON *obj = cJSON_Parse("{\"a\": {\"b\": 1}}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"move\", \"from\": \"/a/b\", \"path\": \"/c\"}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result == 0);
    assert(cJSON_GetObjectItemCaseSensitive(obj, "c")->valueint == 1);
    assert(cJSON_GetObjectItemCaseSensitive(obj, "a")->child == NULL); // b removed
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test ApplyPatches: "copy" operation */
void test_cJSONUtils_ApplyPatches_copy_op(void) {
    cJSON *obj = cJSON_Parse("{\"a\": {\"b\": [1, 2]}}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"copy\", \"from\": \"/a/b\", \"path\": \"/c\"}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result == 0);
    cJSON *c = cJSON_GetObjectItemCaseSensitive(obj, "c");
    assert(cJSON_GetArraySize(c) == 2);
    assert(cJSON_GetArrayItem(c, 0)->valueint == 1);
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test GeneratePatches with case sensitivity */
void test_cJSONUtils_GeneratePatches_case_sensitive(void) {
    cJSON *from = cJSON_Parse("{\"Key\": 1}");
    cJSON *to   = cJSON_Parse("{\"key\": 2}");
    cJSON *patch_insensitive = cJSONUtils_GeneratePatches(from, to);
    cJSON *patch_sensitive   = cJSONUtils_GeneratePatchesCaseSensitive(from, to);

    // case-insensitive: treats Key == key → replace
    assert(cJSON_GetArraySize(patch_insensitive) == 1);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(cJSON_GetArrayItem(patch_insensitive, 0), "op")->valuestring, "replace") == 0);

    // case-sensitive: Key != key → remove + add
    assert(cJSON_GetArraySize(patch_sensitive) == 2);

    cJSON_Delete(from);
    cJSON_Delete(to);
    cJSON_Delete(patch_insensitive);
    cJSON_Delete(patch_sensitive);
}

/* Test MergePatch: patch is null → target becomes null */
void test_cJSONUtils_MergePatch_null_handling(void) {
    cJSON *target = cJSON_Parse("{\"a\":1}");
    cJSON *null_patch = cJSON_CreateNull();
    cJSON *result = cJSONUtils_MergePatch(target, null_patch);
    assert(cJSON_IsNull(result));
    cJSON_Delete(null_patch);
    // target is consumed; only delete result
    cJSON_Delete(result);
}

/* Test GenerateMergePatch: empty objects */
void test_cJSONUtils_GenerateMergePatch_empty_objects(void) {
    cJSON *from = cJSON_CreateObject();
    cJSON *to   = cJSON_CreateObject();
    cJSON *patch = cJSONUtils_GenerateMergePatch(from, to);
    assert(patch == NULL); // no diff → NULL patch
    cJSON_Delete(from);
    cJSON_Delete(to);
    // patch is NULL, so no need to delete
}

/* Test FindPointerFromObjectTo: target not in subtree */
void test_cJSONUtils_FindPointer_target_not_found(void) {
    cJSON *root = cJSON_Parse("{\"a\": 1}");
    cJSON *other = cJSON_CreateNumber(2);
    char *ptr = cJSONUtils_FindPointerFromObjectTo(root, other);
    assert(ptr == NULL);
    cJSON_Delete(root);
    cJSON_Delete(other);
}

/* Test GetPointer: invalid JSON pointer syntax */
void test_cJSONUtils_GetPointer_invalid_syntax(void) {
    cJSON *obj = cJSON_Parse("{\"a\": 1}");
    cJSON *res1 = cJSONUtils_GetPointer(obj, "/a~");      // incomplete escape
    cJSON *res2 = cJSONUtils_GetPointer(obj, "/a/");      // trailing slash with no child
    assert(res1 == NULL);
    assert(res2 == NULL);
    cJSON_Delete(obj);
}

/* Test ApplyPatches: invalid op field */
void test_cJSONUtils_ApplyPatches_invalid_op(void) {
    cJSON *obj = cJSON_Parse("{}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"invalid_op\", \"path\": \"/x\", \"value\": 1}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result != 0); // should fail
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test ApplyPatches: missing 'path' */
void test_cJSONUtils_ApplyPatches_missing_path(void) {
    cJSON *obj = cJSON_Parse("{}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"add\", \"value\": 1}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result == 2); // code for malformed patch (missing path)
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test GeneratePatches: deep nested object diff */
void test_cJSONUtils_GeneratePatches_deep_nested(void) {
    cJSON *from = cJSON_Parse("{\"a\":{\"b\":{\"c\":1}}}");
    cJSON *to   = cJSON_Parse("{\"a\":{\"b\":{\"c\":2, \"d\":3}}}");
    cJSON *patches = cJSONUtils_GeneratePatches(from, to);
    assert(cJSON_GetArraySize(patches) == 2);

    cJSON *p1 = cJSON_GetArrayItem(patches, 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p1, "op")->valuestring, "replace") == 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p1, "path")->valuestring, "/a/b/c") == 0);
    assert(cJSON_GetObjectItemCaseSensitive(p1, "value")->valueint == 2);

    cJSON *p2 = cJSON_GetArrayItem(patches, 1);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p2, "op")->valuestring, "add") == 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p2, "path")->valuestring, "/a/b/d") == 0);
    assert(cJSON_GetObjectItemCaseSensitive(p2, "value")->valueint == 3);

    cJSON_Delete(from);
    cJSON_Delete(to);
    cJSON_Delete(patches);
}

/* Test MergePatch: recursive null removal */
void test_cJSONUtils_MergePatch_recursive_null_removal(void) {
    cJSON *target = cJSON_Parse("{\"a\":{\"b\":1,\"c\":2},\"d\":3}");
    cJSON *patch  = cJSON_Parse("{\"a\":{\"b\":null},\"d\":null}");
    cJSON *result = cJSONUtils_MergePatch(target, patch);
    assert(result != NULL);
    assert(cJSON_GetObjectItemCaseSensitive(result, "d") == NULL);
    cJSON *a = cJSON_GetObjectItemCaseSensitive(result, "a");
    assert(a != NULL);
    assert(cJSON_GetObjectItemCaseSensitive(a, "b") == NULL);
    assert(cJSON_GetObjectItemCaseSensitive(a, "c")->valueint == 2);
    cJSON_Delete(patch);
    // target consumed; only delete result
    cJSON_Delete(result);
}

/* Test GenerateMergePatch: identical objects → NULL */
void test_cJSONUtils_GenerateMergePatch_identical(void) {
    cJSON *from = cJSON_Parse("{\"x\": [1,2,{\"y\":true}]}");
    cJSON *to   = cJSON_Parse("{\"x\": [1,2,{\"y\":true}]}");
    cJSON *patch = cJSONUtils_GenerateMergePatch(from, to);
    assert(patch == NULL); // no diff
    cJSON_Delete(from);
    cJSON_Delete(to);
    // patch is NULL, no need to free
}

/* Test SortObjectCaseSensitive: mixed case keys */
void test_cJSONUtils_SortObjectCaseSensitive_mixed_case(void) {
    cJSON *obj = cJSON_Parse("{\"Zoo\":1,\"apple\":2,\"Banana\":3,\"ant\":4}");
    cJSONUtils_SortObjectCaseSensitive(obj);

    cJSON *child = obj->child;
    assert(strcmp(child->string, "Banana") == 0);
    child = child->next;
    assert(strcmp(child->string, "Zoo") == 0);
    child = child->next;
    assert(strcmp(child->string, "ant") == 0);
    child = child->next;
    assert(strcmp(child->string, "apple") == 0);

    cJSON_Delete(obj);
}

/* Test pointer encoding/decoding round-trip for special chars */
void test_cJSONUtils_pointer_encoding_roundtrip(void) {
    const char *original = "a/~0~1b";
    size_t encoded_len = pointer_encoded_length((const unsigned char*)original);
    unsigned char *encoded = (unsigned char*)cJSON_malloc(encoded_len + 1);
    encode_string_as_pointer(encoded, (unsigned char*)original);

    // expected: "a/~0~1~1b" → becomes "a/~0~1~1b" but actually:
    // '/' → ~1, '~' → ~0
    // so "a/~0~1b" → "a~1~00~01b"
    const char *expected = "a~1~00~01b";
    assert(strcmp((char*)encoded, expected) == 0);

    // Now decode in-place
    decode_pointer_inplace(encoded);
    assert(strcmp((char*)encoded, original) == 0);

    cJSON_free(encoded);
}

/* Test MergePatch: array is replaced as atomic value (RFC 7396) */
void test_cJSONUtils_MergePatch_array_atomic_replacement(void) {
    cJSON *target = cJSON_Parse("{\"a\": [1, 2]}");
    cJSON *patch  = cJSON_Parse("{\"a\": [3, 4]}");
    cJSON *result = cJSONUtils_MergePatch(target, patch);
    assert(cJSON_GetArraySize(cJSON_GetObjectItemCaseSensitive(result, "a")) == 2);
    assert(cJSON_GetArrayItem(cJSON_GetObjectItemCaseSensitive(result, "a"), 0)->valueint == 3);
    cJSON_Delete(patch);
    cJSON_Delete(result); // target consumed
}

/* Test GenerateMergePatch: array diff → full replacement */
void test_cJSONUtils_GenerateMergePatch_array_diff(void) {
    cJSON *from = cJSON_Parse("{\"list\": [1, 2]}");
    cJSON *to   = cJSON_Parse("{\"list\": [1, 2, 3]}");
    cJSON *patch = cJSONUtils_GenerateMergePatch(from, to);
    // Should treat array as atomic → full replace
    assert(cJSON_IsObject(patch));
    cJSON *list_patch = cJSON_GetObjectItemCaseSensitive(patch, "list");
    assert(cJSON_GetArraySize(list_patch) == 3);
    cJSON_Delete(from);
    cJSON_Delete(to);
    cJSON_Delete(patch);
}

/* Test ApplyPatches: add to array end using "-" */
void test_cJSONUtils_ApplyPatches_add_to_array_end(void) {
    cJSON *obj = cJSON_Parse("{\"arr\": [1]}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"add\", \"path\": \"/arr/-\", \"value\": 2}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result == 0);
    cJSON *arr = cJSON_GetObjectItemCaseSensitive(obj, "arr");
    assert(cJSON_GetArraySize(arr) == 2);
    assert(cJSON_GetArrayItem(arr, 1)->valueint == 2);
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test ApplyPatches: add to specific array index */
void test_cJSONUtils_ApplyPatches_add_to_array_index(void) {
    cJSON *obj = cJSON_Parse("{\"arr\": [1, 3]}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"add\", \"path\": \"/arr/1\", \"value\": 2}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result == 0);
    cJSON *arr = cJSON_GetObjectItemCaseSensitive(obj, "arr");
    assert(cJSON_GetArraySize(arr) == 3);
    assert(cJSON_GetArrayItem(arr, 0)->valueint == 1);
    assert(cJSON_GetArrayItem(arr, 1)->valueint == 2);
    assert(cJSON_GetArrayItem(arr, 2)->valueint == 3);
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test FindPointerFromObjectTo: target is root itself */
void test_cJSONUtils_FindPointer_root_self(void) {
    cJSON *root = cJSON_Parse("{}");
    char *ptr = cJSONUtils_FindPointerFromObjectTo(root, root);
    assert(ptr != NULL);
    assert(strcmp(ptr, "") == 0);
    cJSON_free(ptr);
    cJSON_Delete(root);
}

/* Test robustness: NULL input to public APIs */
void test_cJSONUtils_public_api_null_inputs(void) {
    assert(cJSONUtils_FindPointerFromObjectTo(NULL, NULL) == NULL);
    assert(cJSONUtils_FindPointerFromObjectTo(cJSON_Parse("{}"), NULL) == NULL);
    assert(cJSONUtils_GetPointer(NULL, "/a") == NULL);
    assert(cJSONUtils_GetPointer(cJSON_Parse("{}"), NULL) == NULL);
    assert(cJSONUtils_ApplyPatches(NULL, cJSON_Parse("[]")) != 0);
    assert(cJSONUtils_ApplyPatches(cJSON_Parse("{}"), NULL) != 0);
    assert(cJSONUtils_GeneratePatches(NULL, cJSON_Parse("{}")) == NULL);
    assert(cJSONUtils_GeneratePatches(cJSON_Parse("{}"), NULL) == NULL);
    assert(cJSONUtils_MergePatch(NULL, cJSON_CreateNull()) == NULL);
    assert(cJSONUtils_GenerateMergePatch(NULL, cJSON_Parse("{}")) == NULL);

    cJSON_Delete(cJSON_Parse("{}"));
    cJSON_Delete(cJSON_Parse("[]"));
}


/* Test GetPointer: path with escaped characters (~0 = ~, ~1 = /) */
void test_cJSONUtils_GetPointer_escaped_characters(void) {
    cJSON *obj = cJSON_Parse("{\"a/b\": {\"~c\": 42}}");
    // path to "a/b" → "/a~1b"
    // path to "~c" inside → "/a~1b/~0c"
    cJSON *item = cJSONUtils_GetPointer(obj, "/a~1b/~0c");
    assert(item != NULL);
    assert(cJSON_IsNumber(item));
    assert(item->valueint == 42);
    cJSON_Delete(obj);
}

/* Test GenerateMergePatchCaseSensitive: case mismatch in keys → add + remove */
void test_cJSONUtils_GenerateMergePatchCaseSensitive_mismatch(void) {
    cJSON *from = cJSON_Parse("{\"Name\": \"Alice\"}");
    cJSON *to   = cJSON_Parse("{\"name\": \"Bob\"}");
    cJSON *patch = cJSONUtils_GenerateMergePatchCaseSensitive(from, to);
    assert(patch != NULL);
    assert(cJSON_GetObjectItemCaseSensitive(patch, "Name") != NULL);
    assert(cJSON_IsNull(cJSON_GetObjectItemCaseSensitive(patch, "Name")));
    assert(cJSON_GetObjectItemCaseSensitive(patch, "name") != NULL);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(patch, "name")->valuestring, "Bob") == 0);
    cJSON_Delete(from);
    cJSON_Delete(to);
    cJSON_Delete(patch);
}

/* Test MergePatch: patch contains nested null inside object → delete only that field */
void test_cJSONUtils_MergePatch_nested_null_in_object(void) {
    cJSON *target = cJSON_Parse("{\"user\": {\"name\": \"Alice\", \"age\": 30}}");
    cJSON *patch  = cJSON_Parse("{\"user\": {\"age\": null}}");
    cJSON *result = cJSONUtils_MergePatch(target, patch);
    assert(result != NULL);
    cJSON *user = cJSON_GetObjectItemCaseSensitive(result, "user");
    assert(user != NULL);
    assert(cJSON_GetObjectItemCaseSensitive(user, "name") != NULL);
    assert(cJSON_GetObjectItemCaseSensitive(user, "age") == NULL);
    cJSON_Delete(patch);
    cJSON_Delete(result); // target consumed
}

/* Test ApplyPatches: array index out of bounds in "add" */
void test_cJSONUtils_ApplyPatches_add_array_index_oob(void) {
    cJSON *obj = cJSON_Parse("{\"arr\": [1]}");
    cJSON *patch = cJSON_Parse("[{\"op\": \"add\", \"path\": \"/arr/5\", \"value\": 99}]");
    int result = cJSONUtils_ApplyPatches(obj, patch);
    assert(result != 0); // should fail: cannot add beyond end (only allow at size or "-")
    cJSON_Delete(obj);
    cJSON_Delete(patch);
}

/* Test ApplyPatches: duplicate keys in patch object (should not crash) */
void test_cJSONUtils_ApplyPatches_duplicate_keys_in_patch(void) {
    // cJSON doesn't enforce unique keys, but ApplyPatches reads 'op', 'path', etc. once
    cJSON *obj = cJSON_Parse("{}");
    cJSON *patch = cJSON_Parse("[{\"op\":\"add\",\"path\":\"/x\",\"value\":1, \"op\":\"remove\",\"path\":\"/y\"}]");
    // This is a malformed patch (two "op" fields), but cJSON will use the last one
    int result = cJSONUtils_ApplyPatches(obj, cJSON_Parse("[{\"op\":\"add\",\"path\":\"/x\",\"value\":1}]"));
    assert(result == 0);
    assert(cJSON_GetObjectItemCaseSensitive(obj, "x")->valueint == 1);
    cJSON_Delete(obj);
}

/* Test robustness: extremely deep nesting (should not stack overflow in utils) */
void test_cJSONUtils_very_deep_object_find_pointer(void) {
    cJSON *obj = cJSON_CreateObject();
    cJSON *curr = obj;
    const int depth = 100;
    for (int i = 0; i < depth; i++) {
        cJSON *next = cJSON_CreateObject();
        char key[16];
        snprintf(key, sizeof(key), "k%d", i);
        cJSON_AddItemToObject(curr, key, next);
        curr = next;
    }
    cJSON_AddItemToObject(curr, "leaf", cJSON_CreateNumber(123));

    // Find pointer to leaf
    cJSON *leaf = cJSON_GetObjectItemCaseSensitive(curr, "leaf");
    char *ptr = cJSONUtils_FindPointerFromObjectTo(obj, leaf);
    assert(ptr != NULL);
    assert(strlen(ptr) == 3 * depth + 5); // "/k0/k1/.../k99/leaf"
    cJSON_free(ptr);
    cJSON_Delete(obj);
}

/* Test GeneratePatches: array reordering should produce remove+add (not move) */
void test_cJSONUtils_GeneratePatches_array_reorder(void) {
    cJSON *from = cJSON_Parse("{\"list\": [1, 2]}");
    cJSON *to   = cJSON_Parse("{\"list\": [2, 1]}");
    cJSON *patches = cJSONUtils_GeneratePatches(from, to);
    // cJSON utils treats array as ordered; reordering = remove both, add both in new order
    // But current impl diffs by index, so:
    // - replace /list/0 → 2
    // - replace /list/1 → 1
    assert(cJSON_GetArraySize(patches) == 2);
    cJSON *p0 = cJSON_GetArrayItem(patches, 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p0, "op")->valuestring, "replace") == 0);
    assert(strcmp(cJSON_GetObjectItemCaseSensitive(p0, "path")->valuestring, "/list/0") == 0);
    assert(cJSON_GetObjectItemCaseSensitive(p0, "value")->valueint == 2);
    cJSON_Delete(from);
    cJSON_Delete(to);
    cJSON_Delete(patches);
}